package com.shortchainsystem.link.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shortchainsystem.common.minio.MinioUtil;
import com.shortchainsystem.link.mapper.LinkStatsMapper;
import com.shortchainsystem.link.service.LinkStatsService;
import com.shortchainsystem.model.base.Result;
import com.shortchainsystem.model.constants.LinkConstants;
import com.shortchainsystem.model.domain.link.DO.BrowserStatsDO;
import com.shortchainsystem.model.domain.link.DO.IpLocationStatsDO;
import com.shortchainsystem.model.domain.link.DO.OSStatsDO;
import com.shortchainsystem.model.domain.link.DTO.LinkIdsDto;
import com.shortchainsystem.model.domain.link.DTO.StaticDTO;
import com.shortchainsystem.model.domain.link.Link;
import com.shortchainsystem.model.domain.link.DO.LinkDO;
import com.shortchainsystem.model.domain.link.LinkStats;
import com.shortchainsystem.model.domain.link.VO.AccessHistoryVO;
import com.shortchainsystem.model.domain.link.VO.HistoryStaticVO;
import com.shortchainsystem.model.exception.CustomCodeEnum;
import com.shortchainsystem.link.mapper.LinkMapper;
import com.shortchainsystem.link.service.LinkService;
import com.shortchainsystem.util.AppJwtUtil;
import com.shortchainsystem.util.LinkUtil;
import com.shortchainsystem.util.QRCodeUtils;
import com.shortchainsystem.util.RandomUtil;
import org.json.JSONException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

@Service
public class LinkServiceImpl extends ServiceImpl<LinkMapper, Link> implements LinkService {
    @Autowired
    private LinkMapper linkMapper;
    @Autowired
    private LinkStatsMapper linkStatsMapper;
    @Autowired
    private LinkStatsService linkStatsService;

    @Autowired
    private MinioUtil minioUtil;

    @Override
    @Transactional
    public Result encode(LinkDO linkDO) {
        // 1、判断原链接是否可访问
        if(!LinkUtil.checkUrl(linkDO.getUrls())) return Result.errorResult(CustomCodeEnum.LINK_UNAVAILABLE_ERROR);
        // 2、判断该用户是否没有创建该长链
        List<Link> links = linkMapper.selectList(new LambdaQueryWrapper<Link>()
                .eq(Link::getUserId, linkDO.getUserId())
                .eq(Link::getLongUrl, linkDO.getUrls()));
        if(links!=null && links.size()!=0) return Result.errorResult(CustomCodeEnum.LINK_EXIST_ERROR);
        // 1、处理好属性
        Link link = new Link();
        BeanUtils.copyProperties(linkDO, link);
        link.setLongUrl(linkDO.getUrls());
        wrapLink(link);
        // 3、添加一条新链接
        int i = linkMapper.insert(link);
        // 4、生成二维码图片
        generateQRCodeImage(link);
        return i>0 ? Result.okResult(CustomCodeEnum.SUCCESS) : Result.errorResult(CustomCodeEnum.SERVER_ERROR);
    }

    @Override
    @Transactional
    public Result batchEncode(LinkDO linkDto) {
        // 1、截取多段URL，并且过滤重复
        String[] urls = linkDto.getUrls().split(";");
        urls = Arrays.stream(urls).distinct().toArray(String[]::new);
        Set<String> urlSet = new HashSet<>(Arrays.asList(urls));
        // 2、判断原链接是否可访问，并且判断该用户是否没有创建该长链
        for (String url : urlSet) {
            if(!LinkUtil.checkUrl(url)) return Result.errorResult(CustomCodeEnum.LINK_UNAVAILABLE_ERROR);
            List<Link> links = linkMapper.selectList(new LambdaQueryWrapper<Link>()
                    .eq(Link::getUserId, linkDto.getUserId())
                    .eq(Link::getLongUrl, url));
            if(links!=null && links.size()!=0) return Result.errorResult(CustomCodeEnum.LINK_EXIST_ERROR);
        }
        // 3、处理好多条新链接属性
        for (String url : urlSet) {
            Link link = new Link();
            BeanUtils.copyProperties(linkDto, link);
            link.setLongUrl(url);
            wrapLink(link);
            // 4、添加短链
            int i = linkMapper.insert(link);
            if(i<=0) return Result.errorResult(CustomCodeEnum.SERVER_ERROR);
        }
        return Result.okResult(CustomCodeEnum.SUCCESS);
    }

    @Override
    @Transactional
    public String decode(String shortKey, HttpServletRequest request) throws JSONException, IOException {
        // 1、获取访问信息
        LinkStats linkStats = LinkUtil.getAccessInfo(request);

        // 2、获取短链ID，并查询短链
        shortKey = shortKey.substring(shortKey.lastIndexOf('/') + 1);
        String id = LinkUtil.shortKeyToId(shortKey);
        Link link = linkMapper.selectById(id);
        link.setVisitCount(link.getVisitCount()+1);
        linkMapper.updateById(link);
        linkStats.setLinkId(id);

        // 3、获取当前访问的用户ID
        String token = request.getHeader("token");
        if(token!=null && !StringUtils.isEmpty(token)) {
            String userId = String.valueOf(AppJwtUtil.getJws(token).getBody().get("id"));
            linkStats.setUserId(userId);
        }else linkStats.setUserId("");

        // 插入访问信息
        linkStatsService.insertOne(linkStats);
        return link.getLongUrl();
    }

    @Override
    public Result getInfo(String shortUrl) {
        // 1、数据库查询短链
        Link link = linkMapper.selectOne(new LambdaQueryWrapper<Link>()
                .eq(Link::getShortUrl, shortUrl));
        if(link==null) return Result.errorResult(CustomCodeEnum.SERVER_ERROR);
        // 2、判断二维码图片是否存在
        if(link.getQrcodeImage()==null) generateQRCodeImage(link);
        return Result.okResult(link);
    }

    @Override
    public Result edit(LinkDO linkDO) {
        // 1、判断原链接是否可访问
        if(!LinkUtil.checkUrl(linkDO.getUrls())) return Result.errorResult(CustomCodeEnum.LINK_UNAVAILABLE_ERROR);
        // 2、判断该用户是否没有创建该长链
        List<Link> linkList = linkMapper.selectList(new LambdaQueryWrapper<Link>()
                .eq(Link::getUserId, linkDO.getUserId())
                .eq(Link::getLongUrl, linkDO.getUrls()));
        if(linkList!=null && linkList.size()==0) return Result.errorResult(CustomCodeEnum.LINK_EXIST_ERROR);
        Link link = new Link();
        BeanUtils.copyProperties(linkDO, link);
        // 3、修改短链
        linkMapper.updateById(link);
        return Result.okResult(CustomCodeEnum.SUCCESS);
    }

    @Override
    @Transactional
    public Result batchDelete(LinkIdsDto linkIdsDto) {
        List<String> linkIds = linkIdsDto.getLinkIds();
        try {
            for (String linkId : linkIds) {
                // 1、删除所有短链数据
                linkStatsService.deleteAll(linkId);
                // 2、删除短链
                linkMapper.deleteById(linkId);
            }
        } catch (Exception e) {
            log.error("批量删除失败，原因：{}", e);
            return Result.errorResult(CustomCodeEnum.SERVER_ERROR);
        }
        return Result.okResult(CustomCodeEnum.SUCCESS);
    }

    @Override
    public Result historyShow(StaticDTO staticDTO, String userId) {
        // 1、获取该用户所创建的所有短链
        List<Link> links = linkMapper.selectList(
                new LambdaQueryWrapper<Link>().eq(Link::getUserId,userId));
        // 2、根据时间范围获取短链的访问数据
        List<LinkStats> allLinkStats = linkStatsService.getAllLinkStats(links, staticDTO);
        // 3、根据时间范围获取概览数据
        HistoryStaticVO hsvo = new HistoryStaticVO();
        hsvo.setVisitCount(links.stream().mapToInt(Link::getVisitCount).sum());
        hsvo.setVisitIpCount(allLinkStats.stream().map(LinkStats::getIpAddress).collect(Collectors.toSet()).size());
        hsvo.setVisitUserCount(allLinkStats.stream().map(LinkStats::getUserId).collect(Collectors.toSet()).size());
        hsvo.setGenerateCount(links.size());
        return  Result.okResult(hsvo);
    }

    @Override
    public Result accessHistory(String shortUrl, String userId) {
        List<AccessHistoryVO> accessHistory = linkStatsMapper.getAccessHistory(shortUrl, userId);
        return Result.okResult(accessHistory);
    }

    @Override
    public Result accessEnvironment(String timeType, String userId) {
        // 1、获取开始时间
        LocalDateTime startTime = getStartTime(timeType);
        // 2、获取所有访问环境数据
        List<BrowserStatsDO> browserStatsDOS = linkStatsMapper.getBrowserCount(userId, startTime);
        // 3、补充
        Map<String, Integer> browserMap = BrowserStatsDO.createBrowserMap();
        for (BrowserStatsDO browserStatsDO : browserStatsDOS) {
            browserMap.put(browserStatsDO.getBrowser(), browserStatsDO.getCount());
        }
        return Result.okResult(browserMap);
    }

    @Override
    public Result accessOS(String timeType, String userId) {
        // 1、获取开始时间
        LocalDateTime startTime = getStartTime(timeType);
        // 2、获取所有访问环境数据
        List<OSStatsDO> osStatsDOS = linkStatsMapper.getOSCount(userId, startTime);
        // 3、补充
        Map<String, Integer> osMap = OSStatsDO.createOSMap();
        for (OSStatsDO osStatsDO : osStatsDOS) {
            osMap.put(osStatsDO.getOs(), osStatsDO.getCount());
        }
        return Result.okResult(osMap);
    }

    @Override
    public Result geographicalDistribution(StaticDTO staticDTO, String userId) {
        // 1、获取开始时间
        LocalDateTime startTime = getStartTime(staticDTO.getTimeType());
        // 2、获取所有地区范围
        List<IpLocationStatsDO> locationStatsDOS = linkStatsMapper
                .getIpLocationCount(userId, startTime,staticDTO.getOs(),staticDTO.getBrowser());
        // 3、补充
        Map<String, Integer> locationMap = IpLocationStatsDO.createLocationMap();
        Set<String> keySet = locationMap.keySet();
        for (IpLocationStatsDO locationStatsDO : locationStatsDOS) {
            for (String province : keySet) {
                if(locationStatsDO.getIpLocation().contains(province)){
                    locationMap.put(province,locationMap.get(province)+locationStatsDO.getCount());
                }
            }
        }
        return Result.okResult(locationMap);
    }

    // 封装链接
    private Link wrapLink(Link link) {
        link.setStatus(LinkConstants.LINK_STATUS_TRUE);
        link.setVisitCount(0);
        link.setEnableFlag(LinkConstants.ENABLE_FLAG_TRUE);
        link.setId(RandomUtil.getRandom(10));
        String shortUrl = LinkUtil.idToShortKey(link.getId());
        if(link.getDomain().equals(LinkConstants.LINK_PREFIX))
            shortUrl = "http://"+link.getDomain()+":9091/"+shortUrl;
        else shortUrl = "http://"+link.getShortUrl()+"/"+shortUrl;
        link.setShortUrl(shortUrl);
        return link;
    }

    // 生成二维码图片
    private void generateQRCodeImage(Link link){
        // 3、生成二维码图片
        try {
            MultipartFile image = QRCodeUtils.generateQRCodeImage(link.getLongUrl(),link.getId());
            // 保存图片至MinIo
            String qrcodeImg = minioUtil.uploadFile("link", image);
            link.setQrcodeImage(qrcodeImg);
            if(!qrcodeImg.equals("")) linkMapper.updateById(link);
        } catch (Exception e) {
            log.error(CustomCodeEnum.QRCODE_GENERATE_ERROR.getErrorMessage());
        }
    }

    // 获取开始时间范围
    private static LocalDateTime getStartTime(String timeType){
        int days = 0;
        if(timeType.equals("1")) days = 1;
        else if(timeType.equals("2")) days = 3;
        else if(timeType.equals("3")) days = 7;
        else if(timeType.equals("4")) days = 30;
        else if(timeType.equals("5")) days = 365;
        LocalDateTime startDateTime = LocalDateTime.now().minusDays(days);
        if(days==0) startDateTime = null;
        return startDateTime;
    }
}
